<template>
  <div
    class="model-viewer-container"
    :style="{ '--background-color': backgroundColor }"
  >
    <!-- 3D 渲染容器 -->
    <div ref="container" class="three-container"></div>

    <!-- 加载指示器 -->
    <div v-if="isLoading" class="loading-overlay">
      <div class="loading-spinner">
        <div class="spinner"></div>
        <p>{{ loadingProgress }}%</p>
      </div>
    </div>

    <!-- 错误提示 -->
    <div v-if="errorMessage" class="error-message">
      <p>{{ errorMessage }}</p>
      <button @click="retryLoad">重试</button>
    </div>
  </div>
</template>

<script>
// 参考文档 http://www.webgl3d.cn/
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import {
  CSS3DObject,
  CSS3DRenderer,
} from "three/examples/jsm/renderers/CSS3DRenderer";
import Stats from "three/examples/jsm/libs/stats.module";

export default {
  name: "DracoModelViewer",
  props: {
    modelUrl: {
      type: String,
      default: "/three/model/bengfang.glb",
    },
    dracoDecoderPath: {
      type: String,
      default: "/three/gltf/",
    },
    backgroundColor: {
      type: String,
      default: "#222232",
    },
    enableControls: {
      type: Boolean,
      default: true,
    },
    modelData: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      scene: null,
      camera: null,
      renderer: null,
      controls: null,
      model: null,
      animationId: null,
      isLoading: false,
      loadingProgress: 0,
      errorMessage: null,
      mixer: null,
      animations: [],
      clock: null,
      stats: null,
      css3dRenderer: null,
      labelArr: [],
      raycaster: new THREE.Raycaster(),
      mouse: new THREE.Vector2(),
      cameraRotation: new THREE.Vector3(),
      cameraPosition: new THREE.Vector3(),
      targetLabelScale: 1,
      labelScale: 0.02,
      // modelData: [
      //   {
      //     id: 1,
      //     name: "出厂压力",
      //     value: "0.4Mpa",
      //     position: {
      //       x: -0.5,
      //       y: 0.6,
      //       z: 1.55,
      //     },
      //   },
      //   {
      //     id: 2,
      //     name: "瞬时流量",
      //     value: "316.0m³/h",
      //     position: {
      //       x: -0.5,
      //       y: 1.6,
      //       z: 1.55,
      //     },
      //   },
      // ],
    };
  },
  mounted() {
    this.clock = new THREE.Clock();
    this.initThree();
    this.loadModel();
    window.addEventListener("mousedown", this.onMouseDown);
  },
  beforeDestroy() {
    this.stopAnimation();
    this.cleanup();
    window.removeEventListener("mousedown", this.onMouseDown);
  },
  methods: {
    /**
     * 初始化Three.js场景
     */
    initThree() {
      const W = this.$refs.container.clientWidth;
      const H = this.$refs.container.clientHeight;

      // 创建性能监测器
      // this.stats = new Stats();
      // this.$refs.container.appendChild(this.stats.dom);

      // 创建CSS3D渲染器
      this.css3dRenderer = new CSS3DRenderer();
      this.css3dRenderer.setSize(W, H);
      this.css3dRenderer.domElement.style.position = "absolute";
      this.css3dRenderer.domElement.style.top = 0;
      this.$refs.container.appendChild(this.css3dRenderer.domElement);

      // 创建渲染器
      this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
      this.renderer.setSize(W, H);
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.outputEncoding = THREE.sRGBEncoding;
      this.$refs.container.appendChild(this.renderer.domElement);

      // 创建场景
      // const pmremGenerator = new THREE.PMREMGenerator(this.renderer);
      this.scene = new THREE.Scene();
      this.scene.background = new THREE.Color(this.backgroundColor);
      // this.scene.environment = pmremGenerator.fromScene(
      //   new RoomEnvironment(),
      //   0.04
      // ).texture;

      // 创建相机
      const aspectRatio = W / H;
      this.camera = new THREE.PerspectiveCamera(40, aspectRatio, 0.1, 10000);
      // this.camera.position.set(this.cameraPosition);

      // 添加控制器
      if (this.enableControls) {
        this.controls = new OrbitControls(this.camera, this.$refs.container);
        this.controls.enableDamping = true;
        this.controls.dampingFactor = 0.05;
      }

      // 添加光源
      const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
      this.scene.add(ambientLight);

      const directionalLight = new THREE.DirectionalLight(0xffffff, 10);
      directionalLight.position.set(20, 20, 20);
      this.scene.add(directionalLight);

      const hemisphereLight = new THREE.HemisphereLight(0xffffbb, 0x080820, 1);
      this.scene.add(hemisphereLight);

      // 监听窗口大小变化
      window.addEventListener("resize", this.onWindowResize);

      // this.updateData();
    },

    /**
     * 监听窗口大小变化
     */
    onWindowResize() {
      if (!this.camera || !this.renderer || !this.css3dRenderer) return;

      const width = this.$refs.container.clientWidth;
      const height = this.$refs.container.clientHeight;

      this.camera.aspect = width / height;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(width, height);
      this.css3dRenderer.setSize(width, height);
    },

    /**
     * 加载模型
     */
    loadModel() {
      this.isLoading = true;
      this.loadingProgress = 0;
      this.errorMessage = null;

      // 创建 GLTFLoader
      const loader = new GLTFLoader();

      // 配置 DRACOLoader
      const dracoLoader = new DRACOLoader();
      dracoLoader.setDecoderPath(this.dracoDecoderPath);
      loader.setDRACOLoader(dracoLoader);

      // 设置加载进度回调
      loader.load(
        this.modelUrl,
        (gltf) => {
          // 模型加载成功
          this.isLoading = false;

          // 清除现有模型
          if (this.model) {
            this.scene.remove(this.model);
          }

          this.model = gltf.scene;
          this.model.position.set(1, 1, 1);
          this.model.scale.set(1, 1, 1);
          this.model.rotation.x = (75 * Math.PI) / 180;
          this.model.rotation.y = (180 * Math.PI) / 180;
          // this.model.rotation.z = (5 * Math.PI) / 180;
          this.scene.add(this.model);

          // 调整相机以适应模型
          this.adjustCameraToFitModel();

          // 处理动画
          if (gltf.animations && gltf.animations.length > 0) {
            this.animations = gltf.animations;
            this.mixer = new THREE.AnimationMixer(this.model);

            // 播放第一个动画
            const action = this.mixer.clipAction(this.animations[0]);
            action.play();
          }

          // 触发加载完成事件
          this.$emit("model-loaded", { model: this.model });

          // 开始动画循环
          this.startAnimation();

          this.createLableArr();
        },
        (progress) => {
          // 加载进度更新
          this.loadingProgress = Math.round(
            (progress.loaded / progress.total) * 100
          );
        },
        (error) => {
          // 加载错误
          this.isLoading = false;
          this.errorMessage = `模型加载失败: ${error.message}`;
          console.error("GLTF 加载错误:", error);
        }
      );
    },

    /**
     * 调整相机以适应模型
     */
    adjustCameraToFitModel() {
      if (!this.model) return;

      // 计算模型的包围盒
      const box = new THREE.Box3().setFromObject(this.model);
      const size = box.getSize(new THREE.Vector3());
      const center = box.getCenter(new THREE.Vector3());

      // 调整相机位置以完全显示模型
      const maxDim = Math.max(size.x, size.y, size.z);
      const fov = this.camera.fov * (Math.PI / 180);
      let cameraZ = Math.abs(maxDim / 2.7 / Math.tan(fov / 2));

      // 添加一些边距
      cameraZ *= 1.5;

      this.camera.position.set(center.x, center.y, cameraZ);
      this.camera.lookAt(center);

      this.camera.updateProjectionMatrix();

      // 更新控制器
      if (this.controls) {
        this.controls.target.set(center.x, center.y, center.z);
        this.controls.update();
      }

      this.cameraRotation = {
        x: this.camera.rotation.x,
        y: this.camera.rotation.y,
        z: this.camera.rotation.z,
      }; // 保存相机初始旋转角度

      this.cameraPosition = this.camera.position.length();
    },

    /**
     * 动画循环
     */
    animate() {
      this.animationId = requestAnimationFrame(this.animate);
      // 更新控制器
      if (this.controls) {
        this.controls.update();
      }

      // 更新动画
      if (this.mixer) {
        const delta = this.clock.getDelta();
        this.mixer.update(delta);
      }

      // 更新性能监视器
      if (this.stats) {
        this.stats.update();
      }

      // 渲染场景
      this.renderer.render(this.scene, this.camera);

      this.css3dRenderer.render(this.scene, this.camera);

      let rotation = {
        x: THREE.MathUtils.radToDeg(
          this.camera.rotation.x - this.cameraRotation.x
        ),
        y: THREE.MathUtils.radToDeg(
          this.camera.rotation.y - this.cameraRotation.y
        ),
        z: THREE.MathUtils.radToDeg(
          this.camera.rotation.z - this.cameraRotation.z
        ),
      };
      const distance = this.camera.position.length();
      let scale = distance / this.cameraPosition;
      this.targetLabelScale = this.labelScale * scale; // 记录目标缩放值
      this.labelArr.forEach((item) => {
        // 使用线性插值平滑过渡缩放值
        const lerpFactor = 0.1; // 插值因子，值越小过渡越平滑
        item.scale.x += (this.targetLabelScale - item.scale.x) * lerpFactor;
        item.scale.y += (this.targetLabelScale - item.scale.y) * lerpFactor;
        item.scale.z += (this.targetLabelScale - item.scale.z) * lerpFactor;
        item.rotation.x = (rotation.x * Math.PI) / 180;
        item.rotation.y = (rotation.y * Math.PI) / 180;
        item.rotation.z = (rotation.z * Math.PI) / 180;
      });
    },

    /**
     * 开始动画
     */
    startAnimation() {
      if (!this.isAnimating) {
        this.isAnimating = true;
        this.animate();
      }
    },

    /**
     * 停止动画
     */
    stopAnimation() {
      if (this.isAnimating) {
        this.isAnimating = false;
        cancelAnimationFrame(this.animationId);
      }
    },

    /**
     * 清理资源
     */
    cleanup() {
      // 清理 Three.js 资源
      if (this.controls) {
        this.controls.dispose();
        this.controls = null;
      }

      if (this.renderer) {
        this.renderer.dispose();
        this.renderer = null;
      }

      if (this.model) {
        this.scene.remove(this.model);
        this.disposeModel(this.model);
        this.model = null;
      }

      if (this.scene) {
        this.scene.traverse((child) => {
          if (child.isMesh) {
            if (child.geometry) {
              child.geometry.dispose();
            }
            if (child.material) {
              if (Array.isArray(child.material)) {
                child.material.forEach((mat) => {
                  if (mat) mat.dispose();
                });
              } else {
                child.material.dispose();
              }
            }
          }
        });
        this.scene = null;
      }

      // 从 DOM 中移除渲染器的 canvas
      if (this.$refs.container && this.renderer) {
        this.$refs.container.removeChild(this.renderer.domElement);
      }

      // 移除事件监听器
      window.removeEventListener("resize", this.onWindowResize);
    },

    /**
     * 释放模型资源
     * @param model
     */
    disposeModel(model) {
      model.traverse((child) => {
        if (child.isMesh) {
          if (child.geometry) {
            child.geometry.dispose();
          }
          if (child.material) {
            if (Array.isArray(child.material)) {
              child.material.forEach((mat) => {
                if (mat) {
                  if (mat.map) mat.map.dispose();
                  if (mat.normalMap) mat.normalMap.dispose();
                  mat.dispose();
                }
              });
            } else {
              if (child.material.map) child.material.map.dispose();
              if (child.material.normalMap) child.material.normalMap.dispose();
              child.material.dispose();
            }
          }
        }
      });
    },
    /**
     * 重试加载
     */
    retryLoad() {
      this.loadModel();
    },

    //更新数据标签
    updateData() {
      if (this.labelArr.length) {
        this.labelArr.forEach((item) => {
          this.destroyObject(item);
        });
        this.labelArr = [];
        this.createLableArr();
      }
    },
    /**
     * 销毁模型
     * @param cssObject
     */
    destroyObject(cssObject) {
      if (!cssObject) return;

      // 从CSS3DRenderer的DOM中移除元素
      if (cssObject.element && cssObject.element.parentNode) {
        cssObject.element.parentNode.removeChild(cssObject.element);
      }

      // 清除CSS3DRenderer的内部对象引用
      if (this.css3dRenderer && this.css3dRenderer.objects) {
        const index = this.css3dRenderer.objects.findIndex(
          (item) => item.object === cssObject
        );
        if (index !== -1) {
          this.css3dRenderer.objects.splice(index, 1);
        }
      }

      // 从Three.js场景中移除对象
      if (this.scene && this.scene.children.includes(cssObject)) {
        const index = this.scene.children.findIndex(
          (item) => item === cssObject
        );
        if (index !== -1) {
          this.scene.children.splice(index, 1);
        }
      }

      // 清空引用
      cssObject = null;
      console.log("CSS3DObject已成功移除");
    },
    createLableArr() {
      let _this = this;
      this.modelData.forEach((item) => {
        let label = _this.createLabel(item);
        label.position.set(item.position.x, item.position.y, item.position.z);
        label.scale.set(this.labelScale, this.labelScale, this.labelScale); //缩放比例
        // label.rotation.x = (-10 * Math.PI) / 180;
        label.name = item.name;
        //label.visible = true;
        _this.scene.add(label);
        _this.labelArr.push(label);

        // const axesHelper = new THREE.AxesHelper(5);
        // label.add(axesHelper);
      });
    },
    createLabel(item) {
      //创建CSS3D
      let div_label = document.createElement("div");
      div_label.className = "div-label";
      let div_ul = document.createElement("ul");
      item.data.forEach((data) => {
        let div_li = document.createElement("li");
        if (data.id) {
          div_label.id = data.id;
        }
        div_li.innerHTML = ``;
        if (data.name) {
          div_li.innerHTML += `<span>${data.name}：</span>`;
        }
        if (data.value) {
          div_li.innerHTML += `<span>${data.value}</span>`;
        }
        if (data.status && data.status === "1") {
          div_li.innerHTML += `<span class="status open"></span>`;
        }
        if (data.status && data.status === "0") {
          div_li.innerHTML += `<span class="status close"></span>`;
        }
        div_ul.appendChild(div_li);
      });
      div_label.appendChild(div_ul);
      let label = new CSS3DObject(div_label);

      return label;
    },
    onMouseDown(event) {
      // 获取容器元素的边界信息
      const container = this.$refs.container.getBoundingClientRect();

      // 计算鼠标在容器内的相对位置 (0-1)
      const x = (event.clientX - container.left) / container.width;
      const y = (event.clientY - container.top) / container.height;

      // 转换为标准化设备坐标 (-1 to +1)
      this.mouse.x = x * 2 - 1;
      this.mouse.y = -(y * 2 - 1);

      // 通过鼠标位置更新射线
      this.raycaster.setFromCamera(this.mouse, this.camera);

      // 检查鼠标点击是否在 CSS3DObject 的屏幕范围内
      let clickedObject = null;
      this.labelArr.forEach((label) => {
        const vector = label.position.clone();
        vector.project(this.camera);

        const halfWidth = this.$refs.container.clientWidth / 2;
        const halfHeight = this.$refs.container.clientHeight / 2;

        const screenX = Math.round(vector.x * halfWidth + halfWidth);
        const screenY = Math.round(-(vector.y * halfHeight) + halfHeight);

        const labelElement = label.element;
        const labelRect = labelElement.getBoundingClientRect();

        if (
          event.clientX >= labelRect.left &&
          event.clientX <= labelRect.right &&
          event.clientY >= labelRect.top &&
          event.clientY <= labelRect.bottom
        ) {
          clickedObject = label;
        }
      });

      if (clickedObject) {
        if (clickedObject?.element?.id == "videoBtn") {
          this.$emit("videoBtnClick");
        }
      } else {
        console.log("未找到交点");
      }
    },
  },
  watch: {
    modelUrl(newUrl) {
      if (newUrl && newUrl !== this.modelUrl) {
        this.loadModel();
      }
    },
    backgroundColor(newColor) {
      if (this.scene) {
        this.scene.background = new THREE.Color(newColor);
      }
    },
    enableControls(newValue) {
      if (newValue && !this.controls && this.camera && this.renderer) {
        this.controls = new OrbitControls(
          this.camera,
          this.renderer.domElement
        );
        this.controls.enableDamping = true;
      } else if (!newValue && this.controls) {
        this.controls.dispose();
        this.controls = null;
      }
    },
    modelData(val) {
      if (!val) return;
      // this.updateData();
    },
  },
};
</script>

<style lang="scss" scoped>
.model-viewer-container {
  width: 100%;
  height: 100%;
  .three-container {
    width: 100%;
    height: 100%;
    background-color: var(--background-color, #f0f0f0);
    overflow: hidden;
    position: relative;

    :deep(.div-label) {
      background-color: #1c3857;
      border: 1px solid #0196fc;
      border-radius: 6px;

      ul {
        padding: 0;
        margin: 0;
        list-style: none;
        li {
          padding: 6px 12px;
          display: flex;
          align-items: center;
          span {
            font-size: 12px;
            &:first-child {
              color: #fff;
            }
            &:nth-child(2) {
              color: #00f0ff;
            }
            &.status {
              margin-left: 12px;
              display: inline-block;
              width: 12px;
              height: 12px;
              background: #777777;
              border: 1px solid #ffffff;
              border-radius: 50%;
            }
            &.open {
              background: #00f0ff;
            }
          }
        }
      }
    }
  }
}
.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: var(--background-color, #f0f0f0);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10;
}

.loading-spinner {
  text-align: center;
  color: white;
}

.spinner {
  width: 40px;
  height: 40px;
  margin: 20px auto;
  border: 4px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  border-top: 4px solid white;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

.error-message {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: rgba(255, 255, 255, 0.9);
  padding: 20px;
  border-radius: 8px;
  text-align: center;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  z-index: 10;
}

.error-message button {
  margin-top: 10px;
  padding: 8px 16px;
  background-color: #4caf50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.error-message button:hover {
  background-color: #45a049;
}
</style>
